其他
Android 15?我想躺着
Vision Pro搭载M2和R1芯片,其中R1是为应对实时传感器处理任务而设计的,它负责处理来自12个摄像头、5个传感器和6个麦克风的数据,M2芯片负责Vision Pro自身的运转性能,因此Vision Pro可以独立使用。
https://juejin.cn/post/7376622203301462055
涉及所有应用的变更 针对Android 15应用的变更 Android 15新功能探索 Android 15 时间线及总结
Support for 16 KB page sizes
系统面临内存压力时缩短应用启动时间:平均缩短 3.16%,谷歌测试的一些应用的改进更为显著(高达 30%) 降低应用程序启动时的耗电量:平均减少 4.56% 相机启动速度更快:热启动速度平均加快 4.48% 冷启动速度平均加快 6.60% 改善系统启动时间:平均改善1.5%(约0.8秒)
https://developer.android.com/guide/practices/page-sizes#build
隐私空间
Launcher
开发的 Launcher 必须被指定为设备的默认应用,即拥有该 ROLE_HOME 角色。 必须 在应用的清单文件中ACCESS_HIDDEN_PROFILES 声明正常权限。
必须有一个单独的启动器容器,用于安装位于私人空间的应用。 用户必须能够隐藏和显示私人空间容器。 用户必须能够锁定和解锁私人空间容器。 锁定期间,私人空间容器内的任何应用都不应可见,或无法通过搜索等机制发现。
应用商店应用
MinSDK 更新
Camera and media
确保应用已正确迁移以使用预测返回手势。 确保 fragment transitions 与预测性返回导航兼容。 放弃动画和框架转换,改用动画器和 androidx 转换。 迁移 FragmentManager 未知的返回堆栈。改用由 FragmentManager Navigation 组件管理的返回堆栈。
数据同步前台服务超时行为
super.onTimeout(startId)
stopSelf()
}
新媒体处理前台服务类型
android:name="Service"
android:foregroundServiceType="mediaProcessing" />
Receiver 启动前台服务的限制
有SYSTEM_ALERT_WINDOW权限时启动前台服务
阻止与堆栈顶部 UID 不匹配的应用启动
执行启动的应用程序以 Android 15 为目标 任务堆栈顶部的应用程序以 Android 15 为目标 任何可见的活动都已选择加入新的保护措施
更安全的Intent
匹配目标 IntentFilter:针对特定组件的 Intent 必须准确匹配目标的 IntentFilter 规范。如果发送 Intent 以启动另一个应用的 Activity,则目标 Intent 组件需要与接收 Activity 声明的 IntentFilter 保持一致。 Intent 必须具有操作:没有操作的 Intent 将不再匹配任何 IntentFilter。这意味着用于启动 Activity或 Service 的 Intent 必须具有明确定义的操作。
StrictMode.setVmPolicy(VmPolicy.Builder()
.detectUnsafeIntentLaunch()
.build()
)
}
Edge-to-edge enforcement
默认透明。 底部偏移被禁用,因此除非应用插入,否则内容将在系统导航栏后面绘制。 setNavigationBarColor和R.attr#navigationBarColor已被弃用,并且不会影响手势导航。 setNavigationBarContrastEnforced并且 R.attr#navigationBarContrastEnforced继续对手势导航没有影响。
不透明度默认设置为 80%,颜色可能与窗口背景相匹配。 底部偏移已禁用,因此除非应用插入,否则内容将在系统导航栏后面绘制。 setNavigationBarColor并R.attr#navigationBarColor默认设置为与窗口背景匹配。窗口背景必须是彩色可绘制对象,此默认设置才会应用。此 API 已弃用,但仍会影响金刚键。 setNavigationBarContrastEnforced默认情况下为 R.attr#navigationBarContrastEnforced真,这会在金刚键中添加 80% 不透明的背景。
默认透明。 顶部偏移被禁用,因此除非应用插入,否则内容会绘制在状态栏后面。 setStatusBarColor和R.attr#statusBarColor已被弃用,并且对 Android 15 没有影响。 setStatusBarContrastEnforced和 R.attr#statusBarContrastEnforced已被弃用,但仍对 Android 15 有影响。
适配Edge-to-edge
如果应用在 Compose 中使用 Material 3 组件不会受到影响 如果应用在 Compose 中使用 Material 2 组件,需手动设置边距。 如果应用使用 View 和 Material 组件,大多数基于视图的 Material 组件(例如BottomNavigationView、BottomAppBar、 NavigationRailView 等)都可以处理边距。但如果使用 Layout 则需要添加 android:fitsSystemWindows="true"。 对于自定义 Composables,需手动设置边距。 如果正在使用 View 、BottomSheet 、SideSheet 或自定义容器,需使用 ViewCompat.setOnApplyWindowInsetsListener 进行监听设置对应边距。对于 RecyclerView 需添加 clipToPadding="false"。
原生 View 适配
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#B82828"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#000"
android:text="Tops" />
<Button
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:background="#000"
android:text="Bottom" />
<Button
android:layout_width="50dp"
android:layout_height="match_parent"
android:background="#000"
android:text="Left" />
<Button
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:background="#000"
android:text="Right" />
</RelativeLayout>
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
}
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_test)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#B82828"
android:fitsSystemWindows="true"
tools:context=".MainActivity"/>
Compose 适配
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
VanillalceTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Box(modifier = Modifier
.fillMaxSize()
.padding(innerPadding)) {
Button {
Text(text = "Top")
}
Button {
Text(text = "Bottom")
}
Button {
Text(text = "Left")
}
Button {
Text(text = "Right")
}
}
}
}
}
}
}
Box(modifier = Modifier.fillMaxSize()) {}
......
}
小结
@JvmOverloads
fun ComponentActivity.enableEdgeToEdge(
statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT),
navigationBarStyle: SystemBarStyle = SystemBarStyle.auto(DefaultLightScrim, DefaultDarkScrim)
) {
val view = window.decorView
val statusBarIsDark = statusBarStyle.detectDarkMode(view.resources)
val navigationBarIsDark = navigationBarStyle.detectDarkMode(view.resources)
val impl = Impl ?: if (Build.VERSION.SDK_INT >= 30) {
EdgeToEdgeApi30()
} else if (Build.VERSION.SDK_INT >= 29) {
EdgeToEdgeApi29()
} else if (Build.VERSION.SDK_INT >= 28) {
EdgeToEdgeApi28()
} else if (Build.VERSION.SDK_INT >= 26) {
EdgeToEdgeApi26()
} else if (Build.VERSION.SDK_INT >= 23) {
EdgeToEdgeApi23()
} else if (Build.VERSION.SDK_INT >= 21) {
EdgeToEdgeApi21()
} else {
EdgeToEdgeBase()
}.also { Impl = it }
impl.setUp(
statusBarStyle, navigationBarStyle, window, view, statusBarIsDark, navigationBarIsDark
)
impl.adjustLayoutInDisplayCutoutMode(window)
}
Stable configuration
TextView 宽度因复杂字母形状而变化
Camera and media
低光增强
提供增强的图像预览,以便用户能够更好地构图低光照片。 在弱光环境下扫描二维码。
val autoExposureModes =
characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES)!!
val lowLightBoostSupported = autoExposureModes.contains(
CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY)
if (lowLightBoostSupported) {
// Enable Low Light Boost (next section)
} else {
// Proceed without Low Light Boost
}
CameraDevice.TEMPLATE_PREVIEW)
if (isLowLightBoostAvailable(cameraId)) {
captureRequestBuilder.set(
CaptureRequest.CONTROL_AE_MODE,
CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY
)
}
// other capture request params
session.setRepeatingRequest(
captureRequestBuilder.build(),
object : CaptureCallback() {
@Override
fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest, result: TotalCaptureResult) {
// verify Low Light Boost AE mode set successfully
result.get(CaptureResult.CONTROL_AE_MODE) ==
CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY
}
},
cameraHandler
)
captureRequestBuilder.build(),
object : CaptureCallback() {
@Override
fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest, result: TotalCaptureResult) {
// check if Low Light Boost is active or inactive
if (result.get(CaptureResult.CONTROL_LOW_LIGHT_BOOST_STATE) ==
CameraMetadata.CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE) {
// Low Light Boost state is active
// Show Moon Icon
} else {
// Low Light Boost state is inactive or AE mode is not set
// to Low Light Boost
// Hide Moon Icon
}
}
},
cameraHandler
)
应用内相机控制
HDR 余量控制
响度控制
val mediaCodec = …
val audioTrack = AudioTrack.Builder()
.setSessionId(sessionId)
.build()
...
// Create new loudness controller that applies the parameters to the MediaCodec
try {
val lcController = LoudnessCodecController.create(mSessionId)
// Starts applying audio updates for each added MediaCodec
}
Developer productivity and tools
OpenJDK 17 更新
PDF 改进
自动语言切换改进
改进的 OpenType 可变字体 API
FontFamily.Builder(
Font.Builder(assets, "RobotoFlex.ttf").build())
.buildVariableFamily())
.build()
精细换行控制
<string name="pixel8pro">The power and brains behind Pixel 8 Pro.</string>
</resources>
<string name="pixel8pro">The power and brains behind <nobreak>Pixel 8 Pro.</nobreak></string>
</resources>
ApplicationStartInfo API
详细的应用程序大小信息
屏幕录制检测
if (state == SCREEN_RECORDING_STATE_VISIBLE) {
// We're being recorded
} else {
// We're not being recorded
}
}
override fun onStart() {
super.onStart()
val initialState =
windowManager.addScreenRecordingCallback(mainExecutor, mCallback)
mCallback.accept(initialState)
}
override fun onStop() {
super.onStop()
windowManager.removeScreenRecordingCallback(mCallback)
}
扩展了 IntentFilter 功能
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:domain="astore.com" />
<uri-relative-filter-group>
<data android:pathPrefix="/auth" />
<data android:query="region=na" />
</uri-relative-filter-group>
<uri-relative-filter-group android:allow="false">
<data android:pathPrefix="/auth" />
<data android:query="mobileoptout=true" />
</uri-relative-filter-group>
<uri-relative-filter-group android:allow="false">
<data android:pathPrefix="/auth" />
<data android:fragmentPrefix="faq" />
</uri-relative-filter-group>
</intent-filter>
查询用户最近选择的“精选照片访问”
val mediaColumns = arrayOf(
FileColumns._ID,
FileColumns.DISPLAY_NAME,
FileColumns.MIME_TYPE,
)
val queryArgs = bundleOf(
// Return only items from the last selection (selected photos access)
QUERY_ARG_LATEST_SELECTION_ONLY to true,
// Sort returned items chronologically based on when they were added to the device's storage
QUERY_ARG_SQL_SORT_ORDER to "${FileColumns.DATE_ADDED} DESC",
QUERY_ARG_SQL_SELECTION to "${FileColumns.MEDIA_TYPE} = ? OR ${FileColumns.MEDIA_TYPE} = ?",
QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
FileColumns.MEDIA_TYPE_IMAGE.toString(),
FileColumns.MEDIA_TYPE_VIDEO.toString()
)
)
小部件预览更新
PUSH API
ComponentName(
appContext,
SociaLiteAppWidgetReceiver::class.java
),
AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
RemoteViews("com.example", R.layout.widget_preview)
)
小部件提供程序可随时调用。提供的预览与其他提供程序信息一起setWidgetPreview保存。AppWidgetService setWidgetPreview通过回调通知主机已更新预览 AppWidgetHost.onProvidersChanged。作为响应,小部件主机将重新加载其所有提供商信息。 当显示小部件预览时,会检查 AppWidgetProviderInfo.generatedPreviewCategories,如果所选类别可用,则调用AppWidgetManager.getWidgetPreview返回此提供程序的已保存预览。
何时调用 setWidgetPreview
在其小部件预览中显示真实数据,例如个性化或最新信息。这些提供商可以在用户登录或在其应用中完成初始配置后设置预览。此后,可以设置定期任务以他们选择的节奏更新预览。这种类型的小部件的示例可以是照片、日历、天气或新闻小部件。 在预览或快捷操作小部件中显示静态信息的提供程序,不显示任何数据。这些提供程序可以在应用首次启动时设置一次预览。此类小部件的示例包括驱动器快捷操作小部件。
画中画
if (pipState.isTransitioningToPip()) {
// Hide UI elements
}
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
if (isInPictureInPictureMode) {
// Unhide UI elements
}
}